﻿using System;
using System.Linq.Expressions;

namespace DynamicToolkit
{
    static class XpathExpressionParser
    {
        public static string Expression(LambdaExpression exp)
        {
            string predicate = Expression(exp.Body);
            return predicate;
        }

        private static string Expression(Expression exp)
        {
            if (exp is BinaryExpression)
            {
                return BinaryExpression(exp as BinaryExpression);
            }
            else if (exp is MemberExpression)
            {
                return MemberExpression(exp as MemberExpression);
            }
            else if (exp is ConstantExpression)
            {
                return ConstantExpression(exp as ConstantExpression);
            }
            else if (exp is UnaryExpression)
            {
                return UnaryExpression(exp as UnaryExpression);
            }

            throw new NotSupportedException(string.Format("Expression is not supported: {0}", exp.GetType().FullName));
        }

        private static string UnaryExpression(UnaryExpression exp)
        {
            if (exp == null)
            {
                throw new ArgumentNullException("exp");
            }

            if (exp.NodeType == ExpressionType.Convert)
            {
                return Expression(exp.Operand);
            }
            else if (exp.Method.GetParameters().Length == 0)
            {
                object obj = exp.Method.Invoke(exp.Operand, null);
                return Quote(obj);
            }

            throw new NotSupportedException();
        }

        private static string ConstantExpression(ConstantExpression exp)
        {
            if (exp == null)
            {
                throw new ArgumentNullException("exp");
            }

            return Quote(exp.Value);
        }

        private static string MemberExpression(MemberExpression exp)
        {
            if (exp == null)
            {
                throw new ArgumentNullException("exp");
            }

            return exp.Member.Name;
        }

        private static string GetPredicateOperator(ExpressionType nodeType)
        {
            switch (nodeType)
            {
                case ExpressionType.AndAlso:
                    return "and";
                case ExpressionType.OrElse:
                    return "or";
                case ExpressionType.Equal:
                    return "=";
                case ExpressionType.NotEqual:
                    return "!=";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.Add:
                    return "+";
                case ExpressionType.Subtract:
                    return "-";
                case ExpressionType.Multiply:
                    return "*";
                case ExpressionType.Divide:
                    return "div";
                default:
                    throw new NotSupportedException(string.Format("The given operator expression is not supported: {0}", nodeType.ToString()));
            }
        }

        private static string BinaryExpression(BinaryExpression exp)
        {
            if (exp == null)
            {
                throw new ArgumentNullException("exp");
            }

            string op = GetPredicateOperator(exp.NodeType);
            string left = Expression(exp.Left);
            string right = Expression(exp.Right);
            return string.Format("{0} {1} {2}", left, op, right);
        }

        private static string Quote(object obj)
        {
            string value;
            TypeCode typeCode = Convert.GetTypeCode(obj);

            switch (typeCode)
            {
                // Simple Quote
                case TypeCode.Char:
                    value = string.Format("'{0}'", obj != null ? obj.ToString() : string.Empty);
                    break;

                // Quote
                case TypeCode.Object:
                case TypeCode.String:
                case TypeCode.DateTime:
                    value = XpathHelper.EscapeXpathValue(obj);
                    break;

                case TypeCode.Boolean:
                    value = (Convert.ToBoolean(obj)) ? "true" : "false";
                    break;

                // NO quote
                case TypeCode.Byte:
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                case TypeCode.Single:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    value = Convert.ToString(obj);
                    break;

                case TypeCode.DBNull:
                case TypeCode.Empty:
                    value = "''";
                    break;
                default:
                    throw new NotSupportedException(string.Format("Type not supported: {0}", typeCode.ToString()));
            }

            return value;
        }
    }
}
